package root.friendship;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import root.historic.AmizadeListener;
import root.historic.event.AmizadeEvent;
import root.item.Item;
import root.item.PublicarPedido;
import root.user.User;
import strategy.GerenciadorAmizadeIF;
import exceptions.FriendshipDoesNotExist;
import exceptions.InvalidAtributException;
import exceptions.InvalidUserException;
import exceptions.ItemDoesNotExist;
import exceptions.YouAreAlreadyFriends;
import exceptions.YouHaveAlreadyRequestedFriendship;
import exceptions.YouHaventReceivedARequest;

/**
 * Classe gerenciadora de amizades.
 * Encarregada das operacoes sobre
 * amizades entre usuarios.
 * Utiliza o padrao Expert.
 */
public class GerenciadorAmizade implements GerenciadorAmizadeIF {

	private Collection<Friendship> friendships;
	private User user;
	private List<AmizadeListener> listeners;
	
	public GerenciadorAmizade(User user) {
		friendships = new ArrayList<Friendship>();
		this.user = user;
		listeners = new ArrayList<AmizadeListener>();
	}
	/**
	 * Adiciona os listeners para o gerenciador de amizades 
	 * @param listener
	 */
	public void addListener(AmizadeListener listener){
		listeners.add(listener);		
	}
	/**
	 * Retorna uma lista contendo os amigos de determinado usuario.
	 * @param request
	 * 	Usuário que terá a lista de amigos retornada.
	 * @return
	 */
	public List<User> getFriends() {
		List<User> friends = new ArrayList<User>();
		for(Friendship next : friendships) {
			if (next.getFriendshipState() == FriendshipState.Accepted) {
				if(!next.getRequester().equals(user))
					friends.add( next.getRequester() );
				else
					friends.add( next.getTarget() );
			}
		}
		return friends;
	}
	
	/**
	 * Uso isso pra os testes
	 * @param request
	 * @return
	 */
	public String getFriendsStringMode() {
		Collection<User> it = getFriends();
		String ret = "";
		boolean occurr = false;
		for (User next : it) {
			if (occurr)
				ret =ret.concat( "; ");
			occurr = true;
			ret = ret.concat(next.toString());
		}
		if (it.size() == 0)
			ret = "O usuário não possui amigos"; //FOREVER ALONE
		return ret;
	}
	
	/**
	 * Verifies if a has a pending request to b
	 * @param a
	 * @param b
	 * @return
	 */
	public boolean hasPendingRequest(User location) {
		return friendships.contains(new Friendship(user,location));
	}
	
	/**
	 * Verifies if a is friend of b
	 * @param a
	 * @param b
	 * @return
	 */
	public boolean isFriend(User location) {
		return friendships.contains(new Friendship(user,location,FriendshipState.Accepted));
	}
	
	/**
	 * Faz um pedido de amizade de A para B.
	 * @param requester
	 * 	A
	 * @param target
	 *  B
	 * @throws YouHaveAlreadyRequestedFriendship
	 *  Se A ja pediu pra ser amigo de B, nao deixo pedir novamente
	 * @throws YouAreAlreadyFriends
	 * 	Se A e B ja sao amigos, nao faz sentido pedir novamente
	 * @throws InvalidUserException
	 * 	Se A ou B forem nulls
	 *  
	 */
	public void requestFriendShip(GerenciadorAmizadeIF gAmizadeRequester) throws YouHaveAlreadyRequestedFriendship, YouAreAlreadyFriends, InvalidUserException {
		User requester = gAmizadeRequester.getOwner();
		if (requester == null ) {
			throw new InvalidUserException();
		}
		if (requester.equals(user)) {
			//Ser ou nao ser meu amigo, eis a questao
		}
		if (gAmizadeRequester.hasPendingRequest(user)) {
			throw new YouHaveAlreadyRequestedFriendship("Requisição já solicitada");
		}
		else if (isFriend(requester)) {
			throw new YouAreAlreadyFriends("Os usuários já são amigos");
		}
		else {
			gAmizadeRequester.addFriendship(new Friendship(requester, user));
		}
	
	}
	
	
	private void denyOrAcceptFriendship(FriendshipState state, GerenciadorAmizadeIF gerenciador) throws YouHaventReceivedARequest, YouAreAlreadyFriends, InvalidUserException {
		User target = gerenciador.getOwner();
		if (target == null) {
			throw new InvalidUserException();
		}
		if (user.equals(target)) {
			//
		}
		if (isFriend(target)) {
			throw new YouAreAlreadyFriends("Os usuários já são amigos");
		}
		else if (!hasPendingRequest(target)) {
			throw new YouHaventReceivedARequest("Requisição de amizade inexistente");
		}
		else {
			removeFriendship(new Friendship(user, target));
			gerenciador.removeFriendship(new Friendship(user, target));
			addFriendship( new Friendship(user, target, state) );
			gerenciador.addFriendship( new Friendship(user, target, state) );
		}
	}
	
	/**
	 * Da Deny em um Request
	 * @param requester
	 * 	O cara que vai dar o Deny
	 * @param target
	 *  O cara que pediu inicialmente a amizade
	 * @throws YouHaventReceivedARequest
	 *  Se "Target" nao tem uma solicitacao pendente pra "Requester", nao faz sentido ele negar
	 * @throws YouAreAlreadyFriends
	 *  Pode ser que essa excecao nunca seja executada, ja que se eles ja sao amigos, nao teria
	 *  solicitacao pendente sobrando, entretanto eu checo mesmo assim.(Ele talvez caia na excecao
	 *  de cima).
	 * @throws InvalidUserException
	 *  Se Requester ou Target forem null
	 */
	public void denyFriendship(GerenciadorAmizadeIF gerenciador) throws YouHaventReceivedARequest, YouAreAlreadyFriends, InvalidUserException {
		denyOrAcceptFriendship(FriendshipState.Negated, gerenciador);
	}
	
	/**
	 * Da Accept em um Request
	 * @param requester
	 * 	O cara que vai dar o Accept
	 * @param target
	 *  O cara que pediu inicialmente a amizade
	 * @throws YouHaventReceivedARequest
	 *  Se "Target" nao tem uma solicitacao pendente pra "Requester", nao faz sentido ele aceitar
	 * @throws YouAreAlreadyFriends
	 *  Pode ser que essa excecao nunca seja executada, ja que se eles ja sao amigos, nao teria
	 *  solicitacao pendente sobrando, entretanto eu checo mesmo assim.(Ele talvez caia na excecao
	 *  de cima).
	 * @throws InvalidUserException
	 *  Se Requester ou Target forem null
	 */
	public void acceptFriendship(GerenciadorAmizadeIF gerenciador) throws YouHaventReceivedARequest, YouAreAlreadyFriends, InvalidUserException {
		User target = gerenciador.getOwner();
		denyOrAcceptFriendship(FriendshipState.Accepted, gerenciador);
		
		disparaAmizadeAprovada(target);
		gerenciador.disparaAmizadeAprovada(user); // notifica o amigo tambem
		
	}
	
	public void disparaAmizadeAprovada(User target){
		for(AmizadeListener historic: listeners){
			historic.notifyAmizadeAprovada(new AmizadeEvent(user, target));
		}
	}
	/**
	 * Deleta o relacionamento entre dois usuarios
	 * @param a
	 * 	Usuario 1
	 * @param b
	 * 	Usuario 2
	 * @throws FriendshipDoesNotExist
	 */
	public void eraseFriendship(GerenciadorAmizadeIF gerenciador) throws FriendshipDoesNotExist {
		User b = gerenciador.getOwner();
		if (!isFriend(b)) {
			throw new FriendshipDoesNotExist("Amizade inexistente");
		} else {
			removeFriendship(new Friendship(user,b,FriendshipState.Accepted));
			gerenciador.removeFriendship(new Friendship(user,b,FriendshipState.Accepted));
			user.removesAllRequestsBetween(b);
			b.removesAllRequestsBetween(user);
		}
	}
	
	/**
	 * Retorna quem pediu pra ser amigo de a
	 * @param a
	 * @return
	 */
	public String getFriendshipRequestsStringMode() {
		String ret = "";
		boolean occurr = false;
		for (Friendship next : friendships) {
			if(next.getFriendshipState() == FriendshipState.Requested){
				if (occurr) {
					ret = ret.concat( "; ");
				}
				if(!next.getRequester().equals(user))
					ret = ret.concat(next.getRequester().getUsername());
				else 
					ret = ret.concat(next.getTarget().getUsername());
				occurr = true;
			}
		}
		if (occurr == false)
			ret = "Não há requisições";
		return ret;
	}
	
	public String getFriendshipStringMode() {
		String ret = "";
		boolean occurr = false;
		Collection<User> it = getFriends();
		for (User next : it) {
			if (occurr) {
				ret =ret.concat( "; ");
			}
			ret = ret.concat(next.getUsername());
			occurr = true;
		}
		if (occurr == false)
			ret = "O usuário não possui amigos";
		return ret;
	}
	
	/**
	 * Retorna uma lista contendo os usuarios que pediram
	 * para ser amigo de determinaod usuario
	 * @param a
	 * 	Usuario que terá a lista de usuários que querem ser amigos dele
	 * retornada
	 * @return
	 */
	public List<User> getFriendshipRequests() {
		List<User> ret = new ArrayList<User>();
		Iterator<Friendship> it = friendships.iterator();
		while (it.hasNext()) {
			Friendship next = it.next();
			if (next.getFriendshipState() == FriendshipState.Requested) {
				ret.add(next.getRequester());
			}
		}
		return ret;
	}	
	/**
	 * 
	 * @param itemId id do item para o qual ele irá pesquisar 
	 * @return
	 * @throws ItemDoesNotExist
	 */
	public Item getItem(String itemId) throws ItemDoesNotExist{
		Item item = null;
		Iterator<Friendship> it = friendships.iterator();
		while(it.hasNext()){
			Friendship friendship = it.next();
			User user = getFriend(friendship);
			try{
				item = user.getItem(itemId);
				break;
			} catch (ItemDoesNotExist e){
				if(!it.hasNext()) throw new ItemDoesNotExist(e.getMessage());
			}
		}
		return item;
	}
	
	private User getFriend(Friendship friendship){
		return friendship.getRequester().equals(user) ? friendship.getTarget() : friendship.getRequester();
	}
	/**
	 * Retona um dado pedido para um determinado ID
	 * @param pedidoId id do pedido a ser retornado
	 * @return
	 * @throws InvalidAtributException
	 */
	public PublicarPedido getPedidoItem(String pedidoId) throws InvalidAtributException{
		PublicarPedido pedido = null;
		Iterator<Friendship> it = friendships.iterator();
        while(it.hasNext()){
                User user = getFriend(it.next());
                try{
                	pedido = user.getPedidoItem(pedidoId);
                	break;
                } catch (InvalidAtributException e){
                	if(!it.hasNext()) throw new InvalidAtributException(e.getMessage());
                }
        }
        return pedido;
	}
	
	public void removeFriendship(Friendship a){
		friendships.remove(a);
	}
	
	public void addFriendship(Friendship a){
		friendships.add(a);
	}
	
	@Override
	public User getOwner() {
		return user;
	}

}